/*------------------------------------------------------------------------------
#
#	Apple Macintosh Developer Technical Support
#
#	MultiFinder-Aware Simple TextEdit Sample Application
#
#	CPlusTESample
#
#	TESample.cp	-	C++ source
#
#	Copyright © 1989 Apple Computer, Inc.
#	All rights reserved.
#
#	Versions:	
#			1.00 					04/89
#
#	Components:
#			CPlusTESample.make		April 1, 1989
#			TApplicationCommon.h	April 1, 1989
#			TApplication.h			April 1, 1989
#			TDocument.h				April 1, 1989
#			TECommon.h				April 1, 1989
#			TESample.h				April 1, 1989
#			TEDocument.h			April 1, 1989
#			TApplication.cp			April 1, 1989
#			TDocument.cp			April 1, 1989
#			TESample.cp				April 1, 1989
#			TEDocument.cp			April 1, 1989
#			TESampleGlue.a			April 1, 1989
#			TApplication.r			April 1, 1989
#			TESample.r				April 1, 1989
#
#	CPlusTESample is an example application that demonstrates
#	how to initialize the commonly used toolbox managers,
#	operate successfully under MultiFinder, handle desk
#	accessories and create, grow, and zoom windows. The
#	fundamental TextEdit toolbox calls and TextEdit autoscroll
#	are demonstrated. It also shows how to create and maintain
#	scrollbar controls. 
#
#	This version of TESample has been substantially reworked in
#	C++ to show how a "typical" object oriented program could
#	be written. To this end, what was once a single source code
#	file has been restructured into a set of classes which
#	demonstrate the advantages of object-oriented programming.
#
------------------------------------------------------------------------------*/


/*
Segmentation strategy:

    This program has only one segment, since the issues
    surrounding segmentation within a class's methods have
    not been investigated yet. We DO unload the data
    initialization segment at startup time, which frees up
    some memory 

SetPort strategy:

    Toolbox routines do not change the current port. In
    spite of this, in this program we use a strategy of
    calling SetPort whenever we want to draw or make calls
    which depend on the current port. This makes us less
    vulnerable to bugs in other software which might alter
    the current port (such as the bug (feature?) in many
    desk accessories which change the port on OpenDeskAcc).
    Hopefully, this also makes the routines from this
    program more self-contained, since they don't depend on
    the current port setting. 

Clipboard strategy:

    This program does not maintain a private scrap.
    Whenever a cut, copy, or paste occurs, we import/export
    from the public scrap to TextEdit's scrap right away,
    using the TEToScrap and TEFromScrap routines. If we did
    use a private scrap, the import/export would be in the
    activate/deactivate event and suspend/resume event
    routines. 
*/

#include <Types.h>
#include <QuickDraw.h>
#include <Fonts.h>
#include <Events.h>
#include <Controls.h>
#include <Windows.h>
#include <Menus.h>
#include <TextEdit.h>
#include <Dialogs.h>
#include <Desk.h>
#include <Scrap.h>
#include <ToolUtils.h>
#include <Memory.h>
#include <SegLoad.h>
#include <Files.h>
#include <OSUtils.h>
#include <Traps.h>

// our class definitions
#include "TEDocument.h"
#include "TESample.h"

// ExtremeNeg and ExtremePos are used to set up wide open rectangles and regions.
const short kExtremeNeg = -32768;
const short kExtremePos = 32767 - 1; // required to address an old region bug

// kMaxOpenDocuments is used to determine whether a new document can be opened
// or created. We keep track of the number of open documents, and disable the
// menu items that create a new document when the maximum is reached. If the
// number of documents falls below the maximum, the items are enabled again. */
const short	kMaxOpenDocuments = 1;
	
// Define max, min, HiWrd and LoWrd macros for efficiency.
#define max(a,b)		((a) > (b) ? (a) : (b))
#define min(a,b)		((a) < (b) ? (a) : (b))
#define HiWrd(aLong)	(((aLong) >> 16) & 0xFFFF)
#define LoWrd(aLong)	((aLong) & 0xFFFF)

// Our application object, initialized in main(). We make it
// global so our functions which don't belong to any class
// can find the active document.
TESample *gTheApplication;

// main is the entrypoint to the program
int main(void)
{
	// Create our application object. This MUST be the FIRST thing
	// done in main(), since it initializes the Toolbox for us.
	gTheApplication = new TESample;
	if (gTheApplication == nil)		// if we couldn't allocate object (impossible!?)
	  return 0;						// go back to Finder
	
	// Start our main event loop running. This won't return until user quits
	gTheApplication->EventLoop();

	// We always return a value, like good little ANSI worshippers
	return 0;
}

// the constructor for our class, called automatically when we create
// an instance of this class. In this particular case, we only want
// one instance since the constructor does all the menu setups and
// creates our (untitled) document.
TESample::TESample(void)
{
	Handle	menuBar;

	// read menus into menu bar
	menuBar = GetNewMBar(rMenuBar);
	// install menus
	SetMenuBar(menuBar);
	DisposHandle(menuBar);
	// add DA names to Apple menu
	AddResMenu(GetMHandle(mApple), 'DRVR');
	DrawMenuBar();

	// create empty mouse region
	fMouseRgn = NewRgn();
	// create a single empty document
	DoNew();
	// make sure we have a valid cursor region
	AdjustCursor();
}

// Tell TApplication class how much heap we need
long TESample::HeapNeeded(void)
{
	return (kMinSize * 1024);
}

// Calculate a sleep value for WaitNextEvent. This takes into account the things
// that DoIdle does with idle time.

unsigned long TESample::SleepVal(void)
{
	unsigned long sleep;

	sleep = kMaxSleepTime;				// default value for sleep
	// if we aren't in background, let document tell us how long to sleep
	if ((!fInBackground) && (fCurDoc != nil))
	  sleep = min(sleep,fCurDoc->CalcIdle());
	return sleep;
}

// This is called whenever we get a null event et al.
// It takes care of necessary periodic actions. For this program,
// it calls TEIdle.

void TESample::DoIdle(void)
{
	TEDocument* fTECurDoc = (TEDocument*) fCurDoc;

	if ( fTECurDoc != nil )
	  fTECurDoc->DoIdle();
} // DoIdle

// Change the cursor's shape, depending on its position. This also calculates a
// region that includes the cursor for WaitNextEvent.

void TESample::AdjustCursor(void)
{
	TEDocument* fTECurDoc = (TEDocument*) fCurDoc;

	// notice that we don't change cursor if front window isn't ours
	if ( (!fInBackground) && (fTECurDoc != nil) )
	  {
		RgnHandle	arrowRgn;
		RgnHandle	iBeamRgn;
		Point		mouse;
		
		// get mouse location and convert to global coordinates
		GetMouse(&mouse);
		LocalToGlobal(&mouse);

		// calculate regions for different cursor shapes
		arrowRgn = NewRgn();
		iBeamRgn = NewRgn();
		// start arrowRgn wide open
		SetRectRgn(arrowRgn, kExtremeNeg, kExtremeNeg, kExtremePos, kExtremePos);
		// calculate iBeamRgn
		fTECurDoc->GetVisTERgn(iBeamRgn);
		// subtract iBeamRgn from arrowRgn 
		DiffRgn(arrowRgn, iBeamRgn, arrowRgn);

		// change the cursor and the region parameter 
		if (PtInRgn(mouse, iBeamRgn))
		  {
			SetCursor(*GetCursor(iBeamCursor));
			CopyRgn(iBeamRgn, fMouseRgn);
		  }
		else
		  {
			SetCursor(&qd.arrow);
			CopyRgn(arrowRgn, fMouseRgn);
		  }
		// get rid of regions we don't need anymore
		DisposeRgn(arrowRgn);
		DisposeRgn(iBeamRgn);
	  }
} // AdjustCursor

// Enable and disable menus based on the current state. The
// user can only select enabled menu items. We set up all the
// menu items before calling MenuSelect or MenuKey, since
// these are the only times that a menu item can be selected.
// Note that MenuSelect is also the only time the user will
// see menu items. This approach to deciding what enable/
// disable state a menu item has the advantage of
// concentrating all the decision-making in one routine, as
// opposed to being spread throughout the application. Other
// application designs may take a different approach that may
// or may not be as valid. 

void TESample::AdjustMenus(void)
{
	WindowPtr	frontmost;
	MenuHandle	menu;
	long		offset;
	Boolean		undo;
	Boolean		cutCopyClear;
	Boolean		paste;
	TEDocument* fTECurDoc = (TEDocument*) fCurDoc;

	frontmost = FrontWindow();

	menu = GetMHandle(mFile);
	if ( fDocList->NumDocs() < kMaxOpenDocuments )
	  EnableItem(menu, iNew);			// New is enabled when we can open more documents 
	else DisableItem(menu, iNew);
	if ( frontmost != (WindowPtr) nil )	// Close is enabled when there is a window to close 
	  EnableItem(menu, iClose);
	else DisableItem(menu, iClose);

	menu = GetMHandle(mEdit);
	undo = false;
	cutCopyClear = false;
	paste = false;
	if (frontmost != nil)
	  {
		if (fTECurDoc == nil)
		  {
			undo = true;				// all editing is enabled for DA windows 
			cutCopyClear = true;
			paste = true;
		  }
		else
		  {
			// Cut, Copy, and Clear is enabled for app. windows with selections 
			if ( fTECurDoc->HaveSelection() )
			  cutCopyClear = true;
			// If we have any TEXT in the scrap, enable paste
			if ( GetScrap(nil, 'TEXT', &offset) )
				paste = true; 
		  }
	  }
	if ( undo )
	  EnableItem(menu, iUndo);
	else DisableItem(menu, iUndo);
	if ( cutCopyClear )
	  {
		EnableItem(menu, iCut);
		EnableItem(menu, iCopy);
		EnableItem(menu, iClear);
	  } 
	else
	  {
		DisableItem(menu, iCut);
		DisableItem(menu, iCopy);
		DisableItem(menu, iClear);
	  }
	if ( paste )
		EnableItem(menu, iPaste);
	else DisableItem(menu, iPaste);
} // AdjustMenus


// This is called when an item is chosen from the menu bar (after calling
// MenuSelect or MenuKey). It does the right thing for each command.

void TESample::DoMenuCommand(short menuID, short menuItem)
{
	short		itemHit;
	Str255		daName;
	short		daRefNum;
	WindowPtr	window;
	TEDocument* fTECurDoc = (TEDocument*) fCurDoc;

	window = FrontWindow();
	switch ( menuID )
	  {
		case mApple:
			switch ( menuItem )
			  {
				case iAbout:		// bring up alert for About 
					itemHit = Alert(rAboutAlert, nil);
					break;
				default:			// all non-About items in this menu are DAs et al 
					GetItem(GetMHandle(mApple), menuItem, daName);
					daRefNum = OpenDeskAcc(daName);
					break;
			  }
			break;
		case mFile:
			switch ( menuItem )
			  {
				case iNew:
					DoNew();
					break;
				case iClose:
					if (fTECurDoc != nil)
					  {
						fDocList->RemoveDoc(fTECurDoc);
						delete fTECurDoc;
					  }
					else CloseDeskAcc(((WindowPeek) fWhichWindow)->windowKind);
					break;
				case iQuit:
					Terminate();
					break;
			  }
			break;
		case mEdit:					// call SystemEdit for DA editing & MultiFinder 
			if ( !SystemEdit(menuItem-1) )
			  {
				switch ( menuItem )
				  {
					case iCut:
						fTECurDoc->DoCut();
						break;
					case iCopy:
						fTECurDoc->DoCopy();
						break;
					case iPaste:
						fTECurDoc->DoPaste();
						break;
					case iClear:
						fTECurDoc->DoClear();
						break;
				   }
			  }
			break;
	  }
	HiliteMenu(0);					// unhighlight what MenuSelect (or MenuKey) hilited 
} // DoMenuCommand

// Create a new document and window. 

void TESample::DoNew(void)
{
	TEDocument* tDoc;

	tDoc = new TEDocument(rDocWindow);
	// if we didn't get an allocation error, add it to list
	if (tDoc != nil)
	  fDocList->AddDoc(tDoc);
} // DoNew

// Clean up the application and exits. We close all of the windows so that
// they can update their documents.

void TESample::Terminate(void)
{
	// FIX: close all docs
	ExitLoop();
} // Terminate

